Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions assets/skills/inbox/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
name: inbox
description: Arm and verify the package-owned Conversations watcher for direct messages and subscribed channels.
---

# Inbox

Use the maintained `@hasna/conversations` surfaces. There is no separate
`inbox` executable to install or copy.

## Prepare the subscriptions

Subscribe the session identity to every channel that can change its work:

```bash
conversations channel subscribe <channel> --from <agent> --preview-chars 320
```

Read the subscriptions back before arming:

```bash
conversations channel subscriptions --from <agent> --json
```

The readback must contain the intended, non-empty channel set. Each row must
carry a seeded `since_message_id`, so the first watch cycle starts from the
subscription baseline instead of replaying history.

## Arm the watcher

First prove the hosted heartbeat path works:

```bash
conversations agents heartbeat --from <agent> --json
```

Only after that command succeeds, arm:

```bash
conversations watch --from <agent> --all --interval 60000 --full-content
```

`--all` watches direct messages plus every subscribed channel. Several
comma-separated identities may be supplied to `--from`; reads are the union
and the first identity is primary for writes.

The watcher reports repeated poll failures as `DEGRADED` and announces
`RECOVERED` after a successful poll. Treat those lines as visibility state,
not as message content.

## Manual fallback when hosted watch is degraded

If the heartbeat command fails, do not claim the watcher is armed. Keep the
subscription baseline and use bounded manual reads until the hosted path is
healthy:

```bash
conversations digest <channel> --since <ISO8601> --json
conversations digest --to <agent> --since <ISO8601> --json
conversations blockers --from <agent> --json
```

Page every digest through `has_more` and `next_cursor`. Preserve the newest
successfully read timestamp or cursor between coordination passes. A manual
read is degraded service: say so explicitly, schedule the next bounded pass,
and do not describe it as a live monitor.

## Verify delivery, not just process lifetime

Have a different agent send one uniquely labelled canary to a subscribed
channel and one direct message. The watcher must surface both within one poll
interval. A running process, successful subscription write, or quiet first
poll alone does not prove delivery.

After both canaries arrive, record the local runtime gate:

```bash
instructions managed-skills status --from <agent> --delivery-verified --json
```

`--delivery-verified` is an evidence assertion, not a probe. Use it only in the
same acceptance pass that observed both canaries.

`conversations watch` does not monitor Todos assignments. If the session also
needs task-assignment awareness, keep that as a separate bounded Todos read;
do not add another Conversations wrapper or executable.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"files": [
"dist",
"assets/skills/inbox/SKILL.md",
"dashboard/dist",
"LICENSE",
"README.md"
Expand All @@ -33,8 +34,8 @@
"generate:sdk": "bun run scripts/generate-sdk.ts",
"kit:check": "bunx @hasna/contracts vendor-kit --check",
"typecheck": "tsc --noEmit",
"test": "bun test",
"check:package-secrets": "bun run src/cli/index.tsx package-manager-scan --fail-on-findings --home .",
"test": "env -u HASNA_INSTRUCTIONS_API_URL -u HASNA_INSTRUCTIONS_API_KEY bun test",
"check:package-secrets": "bun run src/cli/index.tsx package-manager-scan --fail-on-findings .",
"dev:cli": "bun run src/cli/index.tsx",
"dev:mcp": "bun run src/mcp/index.ts",
"dev:serve": "bun run src/server/index.ts",
Expand Down
101 changes: 100 additions & 1 deletion src/cli/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import { ensureProjectDashboardStandardConfig } from "../lib/project-dashboard-s
import { ensureGlobalAgentRulesStandardConfig } from "../lib/global-agent-rules-standard.js";
import { ensureDangerousOperationGuardStandardConfig } from "../lib/dangerous-operation-guard-standard.js";
import { ensureCodewithSharedTodosStorageStandardConfig } from "../lib/codewith-shared-todos-storage-standard.js";
import {
inspectManagedSkillRuntimes,
reconcileManagedSkillRuntimes,
type ManagedSkillRuntimeReconcileReport,
} from "../lib/managed-skill-runtimes.js";
import {
ProjectContextError,
PROJECT_CONTEXT_MAX_INPUT_BYTES,
Expand Down Expand Up @@ -73,6 +78,26 @@ function printJson(value: unknown): void {
printLine(JSON.stringify(value, null, 2));
}

function printManagedSkillRuntimeReport(report: ManagedSkillRuntimeReconcileReport): void {
for (const runtime of report.runtimes) {
if (!runtime.skill_present) continue;
const prefix =
runtime.action === "failed"
? chalk.red("[failed]")
: runtime.dry_run
? chalk.yellow("[dry-run]")
: runtime.manual_fallback_ready && !runtime.healthy
? chalk.yellow("[degraded]")
: runtime.action === "unchanged"
? chalk.dim("=")
: chalk.green("✓");
console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} — ${runtime.reason}`);
if (runtime.action === "update") {
console.log(chalk.dim(` skill contracts: ${runtime.skill_contracts_changed}`));
}
}
}

function fmtConfig(c: Config, format: string) {
if (format === "json") return JSON.stringify(c, null, 2);
if (format === "compact") return `${c.slug} [${c.category}/${c.agent}] ${c.kind === "reference" ? "(ref)" : truncateMiddle(c.target_path ?? "(no path)", 72)}`;
Expand Down Expand Up @@ -947,6 +972,8 @@ profileCmd.command("remove <profile> <config>").description("Remove a config fro

profileCmd.command("apply [id]").description("Apply all configs in a profile to disk")
.option("--dry-run", "preview without writing")
.option("--from <agent>", "verify the hosted Conversations heartbeat for this agent")
.option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass")
.option("--auto", "resolve the matching profile for the current machine")
.option("--hostname <hostname>", "override detected hostname for auto resolution")
.option("--os <os>", "override detected OS for auto resolution")
Expand Down Expand Up @@ -993,6 +1020,13 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
console.error(chalk.red(`[failed] ${failure.config_slug}: ${failure.message}`));
}
if (report.failures.length > 0) process.exitCode = 1;
const runtimeReport = await reconcileManagedSkillRuntimes({
dryRun: opts.dryRun,
agent: opts.from,
deliveryVerified: opts.deliveryVerified,
});
printManagedSkillRuntimeReport(runtimeReport);
if (runtimeReport.failed > 0) process.exitCode = 1;
console.log(chalk.dim(`\n${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
} catch (e) { console.error(chalk.red(formatCliError(e))); process.exit(1); }
});
Expand Down Expand Up @@ -1694,9 +1728,59 @@ program
console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 ✓") : chalk.red(String(status.health.unredactedSecretFindings) + " ⚠")} unredacted`);
console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
console.log(chalk.cyan("Skill runtimes:") + ` ${status.health.missingManagedSkillRuntimes === 0 ? chalk.green(`${status.counts.managedSkillRuntimes.healthy} ready`) : chalk.yellow(`${status.health.missingManagedSkillRuntimes} missing`)} (${status.counts.managedSkillRuntimes.skillsPresent} managed skill(s) present)`);
console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
});

// ── managed skill runtimes ──────────────────────────────────────────────────
const managedSkillsCmd = program
.command("managed-skills")
.description("Inspect or reconcile package-owned runtime contracts for installed managed skills");

managedSkillsCmd
.command("status")
.option("--from <agent>", "verify the hosted Conversations heartbeat for this agent")
.option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass")
.option("--json", "output the full local runtime status as JSON")
.action((opts: { deliveryVerified?: boolean; from?: string; json?: boolean }) => {
const report = inspectManagedSkillRuntimes({
agent: opts.from,
deliveryVerified: opts.deliveryVerified,
});
if (opts.json) {
printJson(report);
if (report.missing > 0) process.exitCode = 1;
return;
}
if (report.skills_present === 0) {
console.log(chalk.dim("No managed skills with package-owned runtime contracts are installed."));
return;
}
for (const runtime of report.runtimes) {
if (!runtime.skill_present) continue;
const prefix = runtime.healthy ? chalk.green("✓") : chalk.yellow("!");
console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} — ${runtime.reason}`);
}
if (report.missing > 0) process.exitCode = 1;
});

managedSkillsCmd
.command("apply")
.option("--dry-run", "preview without writing")
.option("--from <agent>", "verify the hosted Conversations heartbeat for this agent")
.option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass")
.option("--json", "output the reconcile report as JSON")
.action(async (opts: { deliveryVerified?: boolean; dryRun?: boolean; from?: string; json?: boolean }) => {
const report = await reconcileManagedSkillRuntimes({
dryRun: opts.dryRun,
agent: opts.from,
deliveryVerified: opts.deliveryVerified,
});
if (opts.json) printJson(report);
else printManagedSkillRuntimeReport(report);
if (report.failed > 0) process.exitCode = 1;
});

// ── diff --all ────────────────────────────────────────────────────────────────
// Extend existing diff command to support --all

Expand Down Expand Up @@ -2073,6 +2157,8 @@ program
.command("bootstrap")
.description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs")
.option("--dry-run", "show what would be installed without doing it")
.option("--from <agent>", "verify the hosted Conversations heartbeat for this agent")
.option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass")
.option("--skip-mcp", "skip MCP server registration")
.action(async (opts) => {
const store = resolveConfigStore();
Expand Down Expand Up @@ -2128,7 +2214,20 @@ program
console.log(chalk.dim(" would run: configs init"));
}

console.log(chalk.bold("\n✓ Bootstrap complete.") + chalk.dim(" Restart Claude Code for MCP servers to activate."));
console.log(chalk.cyan("\nReconciling managed skill runtimes:"));
const runtimeReport = await reconcileManagedSkillRuntimes({
dryRun: opts.dryRun,
agent: opts.from,
deliveryVerified: opts.deliveryVerified,
});
printManagedSkillRuntimeReport(runtimeReport);
if (runtimeReport.failed > 0) {
console.error(chalk.red("\nBootstrap incomplete: a managed skill runtime could not be reconciled."));
process.exitCode = 1;
return;
}

console.log(chalk.bold("\n✓ Bootstrap complete.") + chalk.dim(" Restart agent sessions to load updated integrations."));
});

// ── pull / push aliases ───────────────────────────────────────────────────────
Expand Down
12 changes: 12 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ export { uuid, now, slugify } from "./db/database.js";
// Status contract
export { getConfigsStatus } from "./status.js";
export type { ConfigsStatusContract } from "./status.js";
export {
INBOX_CONVERSATIONS_MINIMUM_VERSION,
inspectManagedSkillRuntimes,
reconcileManagedSkillRuntimes,
} from "./lib/managed-skill-runtimes.js";
export type {
ManagedSkillRuntimeInspection,
ManagedSkillRuntimeOptions,
ManagedSkillRuntimeReconcileReport,
ManagedSkillRuntimeResult,
ManagedSkillRuntimeStatus,
} from "./lib/managed-skill-runtimes.js";

// DB — PostgreSQL migrations
export { PG_MIGRATIONS } from "./db/pg-migrations.js";
Expand Down
Loading
Loading