instar 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ export declare class StateManager {
26
26
  }): ActivityEvent[];
27
27
  get<T>(key: string): T | null;
28
28
  set<T>(key: string, value: T): void;
29
+ delete(key: string): boolean;
29
30
  /**
30
31
  * Write a file atomically — write to .tmp then rename.
31
32
  * Prevents corruption from power loss or disk-full mid-write.
@@ -143,6 +143,19 @@ export class StateManager {
143
143
  fs.mkdirSync(dir, { recursive: true });
144
144
  this.atomicWrite(filePath, JSON.stringify(value, null, 2));
145
145
  }
146
+ delete(key) {
147
+ this.validateKey(key, 'state key');
148
+ const filePath = path.join(this.stateDir, 'state', `${key}.json`);
149
+ if (!fs.existsSync(filePath))
150
+ return false;
151
+ try {
152
+ fs.unlinkSync(filePath);
153
+ return true;
154
+ }
155
+ catch {
156
+ return false;
157
+ }
158
+ }
146
159
  /**
147
160
  * Write a file atomically — write to .tmp then rename.
148
161
  * Prevents corruption from power loss or disk-full mid-write.
@@ -61,6 +61,18 @@ export interface JobDefinition {
61
61
  tags?: string[];
62
62
  /** Telegram topic ID this job reports to (auto-created if not set) */
63
63
  topicId?: number;
64
+ /** Grounding configuration — what context this job needs at session start */
65
+ grounding?: JobGrounding;
66
+ }
67
+ export interface JobGrounding {
68
+ /** Whether this job requires identity grounding before execution */
69
+ requiresIdentity: boolean;
70
+ /** Whether this job processes external/untrusted input (requires security screening) */
71
+ processesExternalInput?: boolean;
72
+ /** Additional context files to inject at job start (relative to .instar/) */
73
+ contextFiles?: string[];
74
+ /** Custom grounding questions the agent must answer before proceeding */
75
+ questions?: string[];
64
76
  }
65
77
  export type JobPriority = 'critical' | 'high' | 'medium' | 'low';
66
78
  export interface JobExecution {
@@ -5,6 +5,11 @@
5
5
  * content via the Telegraph API (telegra.ph). Zero-config,
6
6
  * no rate limits, instant web pages accessible from anywhere.
7
7
  *
8
+ * IMPORTANT: All Telegraph pages are PUBLIC. There is no authentication
9
+ * or access control — anyone with the URL can view the content.
10
+ * Do NOT publish sensitive, private, or confidential information.
11
+ * For private content, use the Cloudflare Tunnel viewer (when configured).
12
+ *
8
13
  * Telegraph API docs: https://telegra.ph/api
9
14
  */
10
15
  /** Valid Telegraph element tags */
@@ -79,7 +84,11 @@ export declare class TelegraphService {
79
84
  */
80
85
  createAccount(shortName: string, authorName?: string): Promise<TelegraphAccount>;
81
86
  /**
82
- * Publish markdown content as a Telegraph page.
87
+ * Publish markdown content as a PUBLIC Telegraph page.
88
+ *
89
+ * WARNING: The published page is publicly accessible to anyone with the URL.
90
+ * Do not publish sensitive or private information.
91
+ *
83
92
  * Returns the page URL and path.
84
93
  */
85
94
  publishPage(title: string, markdown: string): Promise<TelegraphPage>;
@@ -5,6 +5,11 @@
5
5
  * content via the Telegraph API (telegra.ph). Zero-config,
6
6
  * no rate limits, instant web pages accessible from anywhere.
7
7
  *
8
+ * IMPORTANT: All Telegraph pages are PUBLIC. There is no authentication
9
+ * or access control — anyone with the URL can view the content.
10
+ * Do NOT publish sensitive, private, or confidential information.
11
+ * For private content, use the Cloudflare Tunnel viewer (when configured).
12
+ *
8
13
  * Telegraph API docs: https://telegra.ph/api
9
14
  */
10
15
  import fs from 'node:fs';
@@ -51,7 +56,11 @@ export class TelegraphService {
51
56
  }
52
57
  // ── Publishing ─────────────────────────────────────────────────
53
58
  /**
54
- * Publish markdown content as a Telegraph page.
59
+ * Publish markdown content as a PUBLIC Telegraph page.
60
+ *
61
+ * WARNING: The published page is publicly accessible to anyone with the URL.
62
+ * Do not publish sensitive or private information.
63
+ *
55
64
  * Returns the page URL and path.
56
65
  */
57
66
  async publishPage(title, markdown) {
@@ -170,12 +170,12 @@ This routes feedback to the Instar maintainers automatically. Valid types: \`bug
170
170
  **Relationships** — Track people I interact with.
171
171
  - List: \`curl http://localhost:${port}/relationships\`
172
172
 
173
- **Publishing** — Share content as web pages via Telegraph. Instant, zero-config, accessible from anywhere.
173
+ **Publishing** — Share content as PUBLIC web pages via Telegraph. Instant, zero-config, accessible from anywhere.
174
174
  - Publish: \`curl -X POST http://localhost:${port}/publish -H 'Content-Type: application/json' -d '{"title":"Page Title","markdown":"# Content here"}'\`
175
175
  - List published: \`curl http://localhost:${port}/published\`
176
176
  - Edit: \`curl -X PUT http://localhost:${port}/publish/PAGE_PATH -H 'Content-Type: application/json' -d '{"title":"Updated","markdown":"# New content"}'\`
177
177
 
178
- When the user needs to view rendered markdown reports, summaries, documentation publish it and share the link. The URL works on any device, no server tunnel required.
178
+ **⚠ CRITICAL: All Telegraph pages are PUBLIC.** Anyone with the URL can view the content. There is no authentication or access control. NEVER publish sensitive, private, or confidential information through Telegraph. When sharing a link, always inform the user that the page is publicly accessible.
179
179
 
180
180
  **Scripts** — Reusable capabilities in \`.claude/scripts/\`.
181
181
 
@@ -3,6 +3,11 @@
3
3
  *
4
4
  * Jobs define recurring work the agent should perform:
5
5
  * email checks, health probes, content publishing, etc.
6
+ *
7
+ * Grounding-by-default: Jobs are validated for grounding configuration.
8
+ * Missing grounding emits warnings — nudging the practice without breaking
9
+ * existing jobs. Jobs that process external input WITHOUT grounding get
10
+ * louder warnings because they represent a security surface.
6
11
  */
7
12
  import type { JobDefinition } from '../core/types.js';
8
13
  /**
@@ -3,11 +3,23 @@
3
3
  *
4
4
  * Jobs define recurring work the agent should perform:
5
5
  * email checks, health probes, content publishing, etc.
6
+ *
7
+ * Grounding-by-default: Jobs are validated for grounding configuration.
8
+ * Missing grounding emits warnings — nudging the practice without breaking
9
+ * existing jobs. Jobs that process external input WITHOUT grounding get
10
+ * louder warnings because they represent a security surface.
6
11
  */
7
12
  import fs from 'node:fs';
8
13
  import { Cron } from 'croner';
9
14
  const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'];
10
15
  const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
16
+ /** Slugs for lightweight default jobs where grounding is unnecessary. */
17
+ const GROUNDING_EXEMPT_SLUGS = new Set([
18
+ 'health-check',
19
+ 'feedback-retry',
20
+ 'dispatch-check',
21
+ 'update-check',
22
+ ]);
11
23
  /**
12
24
  * Load and validate job definitions from a JSON file.
13
25
  * Throws on invalid structure — fail loud at startup, not at runtime.
@@ -26,10 +38,13 @@ export function loadJobs(jobsFile) {
26
38
  if (!Array.isArray(raw)) {
27
39
  throw new Error(`Jobs file must contain a JSON array, got ${typeof raw}`);
28
40
  }
29
- return raw.map((job, index) => {
41
+ const jobs = raw.map((job, index) => {
30
42
  validateJob(job, index);
31
43
  return job;
32
44
  });
45
+ // Grounding-by-default audit — warn about jobs missing grounding config
46
+ auditGrounding(jobs);
47
+ return jobs;
33
48
  }
34
49
  /**
35
50
  * Validate a single job definition.
@@ -86,5 +101,66 @@ export function validateJob(job, index) {
86
101
  if (exec.args !== undefined && typeof exec.args !== 'string') {
87
102
  throw new Error(`${prefix}: execute.args must be a string if provided, got ${typeof exec.args}`);
88
103
  }
104
+ // Grounding config — validate structure if present
105
+ if (j.grounding !== undefined) {
106
+ validateGrounding(j.grounding, prefix);
107
+ }
108
+ }
109
+ /**
110
+ * Validate grounding configuration structure.
111
+ * Throws on invalid structure — if you're going to declare grounding, do it right.
112
+ */
113
+ function validateGrounding(grounding, prefix) {
114
+ if (!grounding || typeof grounding !== 'object') {
115
+ throw new Error(`${prefix}: "grounding" must be an object if provided`);
116
+ }
117
+ const g = grounding;
118
+ if (typeof g.requiresIdentity !== 'boolean') {
119
+ throw new Error(`${prefix}: grounding.requiresIdentity must be a boolean`);
120
+ }
121
+ if (g.processesExternalInput !== undefined && typeof g.processesExternalInput !== 'boolean') {
122
+ throw new Error(`${prefix}: grounding.processesExternalInput must be a boolean if provided`);
123
+ }
124
+ if (g.contextFiles !== undefined) {
125
+ if (!Array.isArray(g.contextFiles)) {
126
+ throw new Error(`${prefix}: grounding.contextFiles must be an array of strings if provided`);
127
+ }
128
+ for (const f of g.contextFiles) {
129
+ if (typeof f !== 'string' || !f.trim()) {
130
+ throw new Error(`${prefix}: grounding.contextFiles entries must be non-empty strings`);
131
+ }
132
+ }
133
+ }
134
+ if (g.questions !== undefined) {
135
+ if (!Array.isArray(g.questions)) {
136
+ throw new Error(`${prefix}: grounding.questions must be an array of strings if provided`);
137
+ }
138
+ for (const q of g.questions) {
139
+ if (typeof q !== 'string' || !q.trim()) {
140
+ throw new Error(`${prefix}: grounding.questions entries must be non-empty strings`);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ /**
146
+ * Audit loaded jobs for grounding configuration.
147
+ * Emits warnings for jobs missing grounding — the "nudge" layer.
148
+ * Exempt jobs (health-check, dispatch-check, etc.) are skipped silently.
149
+ */
150
+ function auditGrounding(jobs) {
151
+ const ungrounded = [];
152
+ for (const job of jobs) {
153
+ if (!job.enabled)
154
+ continue;
155
+ if (GROUNDING_EXEMPT_SLUGS.has(job.slug))
156
+ continue;
157
+ if (!job.grounding) {
158
+ ungrounded.push(job.slug);
159
+ }
160
+ }
161
+ if (ungrounded.length > 0) {
162
+ console.warn(`[JobLoader] Grounding audit: ${ungrounded.length} enabled job(s) lack grounding config: ${ungrounded.join(', ')}. ` +
163
+ `Add a "grounding" field to declare identity and security requirements.`);
164
+ }
89
165
  }
90
166
  //# sourceMappingURL=JobLoader.js.map
@@ -212,6 +212,18 @@ export class JobScheduler {
212
212
  spawnJobSession(job, reason) {
213
213
  const prompt = this.buildPrompt(job);
214
214
  const sessionName = `job-${job.slug}-${Date.now().toString(36)}`;
215
+ // Write active-job.json BEFORE spawning so the session-start and
216
+ // compaction-recovery hooks can inject job-specific grounding context.
217
+ this.state.set('active-job', {
218
+ slug: job.slug,
219
+ name: job.name,
220
+ description: job.description,
221
+ priority: job.priority,
222
+ sessionName,
223
+ triggeredBy: reason,
224
+ startedAt: new Date().toISOString(),
225
+ grounding: job.grounding ?? null,
226
+ });
215
227
  this.sessionManager.spawnSession({
216
228
  name: sessionName,
217
229
  prompt,
@@ -285,6 +297,11 @@ export class JobScheduler {
285
297
  const job = this.jobs.find(j => j.slug === session.jobSlug);
286
298
  if (!job)
287
299
  return;
300
+ // Clear active-job.json now that the job is done
301
+ const activeJob = this.state.get('active-job');
302
+ if (activeJob?.slug === job.slug) {
303
+ this.state.delete('active-job');
304
+ }
288
305
  // Update job state with completion result
289
306
  const failed = session.status === 'failed' || session.status === 'killed';
290
307
  const existingState = this.state.getJobState(job.slug);
@@ -820,7 +820,10 @@ export function createRoutes(ctx) {
820
820
  }
821
821
  try {
822
822
  const page = await ctx.publisher.publishPage(title, markdown);
823
- res.status(201).json(page);
823
+ res.status(201).json({
824
+ ...page,
825
+ warning: 'This page is PUBLIC. Anyone with the URL can view it.',
826
+ });
824
827
  }
825
828
  catch (err) {
826
829
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",