maka-agent 0.2.0-dev.25.20260908 → 0.2.0-dev.26.20260909

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.
Files changed (65) hide show
  1. package/dist/mcp-capability-provider.js +4 -1
  2. package/dist/pi-tui-mcp-status.js +2 -2
  3. package/dist/pi-tui-pickers.js +19 -29
  4. package/dist/tui-autocomplete-layout.js +3 -1
  5. package/dist/tui-copy-catalog.js +6 -3
  6. package/dist/tui-editor-render.js +36 -0
  7. package/dist/tui-mcp-control.js +41 -6
  8. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  9. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  10. package/node_modules/@maka/core/dist/session.js +29 -4
  11. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  12. package/node_modules/@maka/core/package.json +2 -3
  13. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  14. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  15. package/node_modules/@maka/mcp/dist/index.js +337 -71
  16. package/node_modules/@maka/runtime/dist/agent-run.js +10 -1
  17. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  18. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +20 -1
  19. package/node_modules/@maka/runtime/dist/runtime-kernel.js +120 -13
  20. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  21. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  22. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +17 -1
  23. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  24. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  25. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  26. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  27. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  28. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +68 -168
  29. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  30. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  31. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  32. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  33. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +45 -20
  34. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  35. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  36. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  37. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  38. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  39. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  40. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +4 -1
  41. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  42. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  43. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  44. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  45. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  46. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  47. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  48. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  49. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -85
  50. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +120 -137
  51. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  52. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  53. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  54. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  55. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  56. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  57. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  58. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  59. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  60. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  61. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  62. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  63. package/package.json +1 -1
  64. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  65. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -16,10 +16,11 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { randomUUID } from 'node:crypto';
20
- import { chmod, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
19
+ import { readFile } from 'node:fs/promises';
21
20
  import { dirname, join } from 'node:path';
21
+ import { writeAtomicFile } from './atomic-file-write.js';
22
22
  import { withFileUpdateLock } from './file-update-lock.js';
23
+ import { hardenDirectory } from './stable-storage.js';
23
24
  /** Current on-disk schema version. Unknown versions fail closed on read. */
24
25
  export const CREDENTIAL_SCHEMA_VERSION = 1;
25
26
  export function createFileCredentialStore(workspaceRoot) {
@@ -129,68 +130,14 @@ class FileCredentialStore {
129
130
  }
130
131
  }
131
132
  /**
132
- * Create (or harden) the directory that holds a secret file: 0700, and
133
- * re-chmod a pre-existing looser dir so neither the secret nor the lock can sit
134
- * world-readable. mkdir's mode only applies on creation, so the chmod is what
135
- * fixes an existing dir. On POSIX a chmod failure fails closed (we must not
136
- * write plaintext credentials into a dir we couldn't lock down); no-op on
137
- * Windows. Shared by the writer and the lock so their dir hardening can't drift.
138
- */
139
- async function ensureSecretDir(dir) {
140
- await mkdir(dir, { recursive: true, mode: 0o700 });
141
- await chmodStrict(dir, 0o700);
142
- }
143
- /**
144
- * Owner-only atomic write for a credentials file: a 0700 dir, an exclusive
145
- * 0600 temp ('wx'/O_EXCL so we never follow a pre-planted symlink at a
146
- * predictable path), a durability fence before and after the atomic rename,
147
- * and temp cleanup on failure.
133
+ * Owner-only atomic write for a credentials file. Directory hardening is an
134
+ * explicit credential-store policy; file publication uses the shared legacy
135
+ * JSON writer so its mode, synchronization, and cleanup behavior remains
136
+ * aligned with settings and MCP config.
148
137
  */
149
138
  async function writeSecretFileAtomic(path, contents) {
150
- await ensureSecretDir(dirname(path));
151
- const tempPath = `${path}.${randomUUID()}.tmp`;
152
- try {
153
- const handle = await open(tempPath, 'wx', 0o600);
154
- try {
155
- await handle.writeFile(contents, 'utf8');
156
- await handle.sync();
157
- }
158
- finally {
159
- await handle.close();
160
- }
161
- await chmodStrict(tempPath, 0o600);
162
- await rename(tempPath, path);
163
- await syncDirectory(dirname(path));
164
- }
165
- catch (error) {
166
- await rm(tempPath, { force: true });
167
- throw error;
168
- }
169
- }
170
- async function syncDirectory(path) {
171
- if (process.platform === 'win32')
172
- return;
173
- const handle = await open(path, 'r');
174
- try {
175
- await handle.sync();
176
- }
177
- finally {
178
- await handle.close();
179
- }
180
- }
181
- /**
182
- * chmod that fails loud on POSIX and is best-effort on Windows. A secret file
183
- * or its directory left looser than intended breaks the 0600/0700 boundary, so
184
- * on POSIX we surface the failure rather than write plaintext into it; Windows
185
- * has no POSIX mode, so a failure there is a no-op. One policy for both the
186
- * secret file (0600) and its directory (0700) so they can't drift apart.
187
- */
188
- async function chmodStrict(path, mode) {
189
- if (process.platform === 'win32') {
190
- await chmod(path, mode).catch(() => { });
191
- return;
192
- }
193
- await chmod(path, mode);
139
+ await hardenDirectory(dirname(path));
140
+ await writeAtomicFile(path, contents, { fileMode: 0o600 });
194
141
  }
195
142
  const LOCK_TIMEOUT_MS = 10_000;
196
143
  /**
@@ -220,7 +167,9 @@ const LOCK_TIMEOUT_MS = 10_000;
220
167
  * stays the typed store and callers can't drive the lock directly.
221
168
  */
222
169
  export async function withCredentialFileLock(targetPath, fn, timeoutMs = LOCK_TIMEOUT_MS) {
223
- await ensureSecretDir(dirname(targetPath));
170
+ // Same owner-only hardening the writer applies, so the lock directory can
171
+ // never sit looser than the secret it guards.
172
+ await hardenDirectory(dirname(targetPath));
224
173
  return withFileUpdateLock(targetPath, fn, timeoutMs);
225
174
  }
226
175
  const STORED_CREDENTIAL_KINDS = [
@@ -149,10 +149,13 @@ async function createExecutionStoresForWrite(lease, kind, extension) {
149
149
  listForRecovery: () => run(() => sessionStore.listForRecovery()),
150
150
  readHeaderSnapshot: (sessionId) => run(() => sessionStore.readHeaderSnapshot(sessionId)),
151
151
  readHeaderRecordSnapshot: (sessionId) => run(() => sessionStore.readHeaderRecordSnapshot(sessionId)),
152
- readCatalogRecord: (sessionId) => run(() => sessionStore.readCatalogRecord(sessionId)),
152
+ readCatalogRecord: (sessionId, roleScope) => run(() => sessionStore.readCatalogRecord(sessionId, roleScope)),
153
153
  probeSessionRemoval: (sessionId) => run(() => sessionStore.probeSessionRemoval(sessionId)),
154
154
  readMessagesSnapshot: (sessionId) => run(() => sessionStore.readMessagesSnapshot(sessionId)),
155
155
  readTranscriptMessagesSnapshot: (sessionId, request) => run(() => sessionStore.readTranscriptMessagesSnapshot(sessionId, request)),
156
+ readCoordinationTranscriptIndexState: () => run(() => sessionStore.readCoordinationTranscriptIndexState()),
157
+ appendCoordinationTranscriptIndex: (records) => run(() => sessionStore.appendCoordinationTranscriptIndex(records)),
158
+ readCoordinationTranscriptIndex: (request) => run(() => sessionStore.readCoordinationTranscriptIndex(request)),
156
159
  readTranscriptHighWaterSnapshot: (sessionId) => run(() => sessionStore.readTranscriptHighWaterSnapshot(sessionId)),
157
160
  listTurnsSnapshot: (sessionId) => run(() => sessionStore.listTurnsSnapshot(sessionId)),
158
161
  readHeader: (sessionId) => run(() => sessionStore.readHeader(sessionId)),
@@ -16,11 +16,15 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { randomUUID } from 'node:crypto';
20
- import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
19
+ import { readFile } from 'node:fs/promises';
21
20
  import { dirname, join } from 'node:path';
22
21
  import { MCP_CONFIG_VERSION, createDefaultMcpConfig, isNonLoopbackCleartextHttp, } from '@maka/core/mcp';
22
+ import { writeAtomicFile } from './atomic-file-write.js';
23
23
  import { withProcessLifetimeFileUpdateLock } from './process-lifetime-file-update-lock.js';
24
+ import { hardenDirectory } from './stable-storage.js';
25
+ // Consumers reconcile an already-published write through this store's public
26
+ // boundary; the shared atomic writer itself remains internal to storage.
27
+ export { AtomicFileWriteCommitUnknownError } from './atomic-file-write.js';
24
28
  const MAX_SERVERS = 100;
25
29
  const MAX_ID_LENGTH = 128;
26
30
  const MAX_STRING_LENGTH = 8_192;
@@ -157,34 +161,15 @@ class FileMcpConfigStore {
157
161
  return withProcessLifetimeFileUpdateLock(this.path, operation);
158
162
  }
159
163
  async write(config) {
160
- const dir = dirname(this.path);
161
164
  await this.ensureDirectory();
162
- const tempPath = join(dir, `.mcp-${randomUUID()}.tmp`);
163
- try {
164
- await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, {
165
- encoding: 'utf8',
166
- mode: 0o600,
167
- flag: 'wx',
168
- });
169
- if (process.platform !== 'win32')
170
- await chmod(tempPath, 0o600);
171
- await rename(tempPath, this.path);
172
- if (process.platform !== 'win32')
173
- await chmod(this.path, 0o600);
174
- }
175
- finally {
176
- await rm(tempPath, { force: true }).catch(() => { });
177
- }
165
+ await writeAtomicFile(this.path, `${JSON.stringify(config, null, 2)}\n`, {
166
+ fileMode: 0o600,
167
+ });
178
168
  }
179
169
  ensureDirectory() {
180
170
  if (this.directoryReady)
181
171
  return this.directoryReady;
182
- const dir = dirname(this.path);
183
- const ready = (async () => {
184
- await mkdir(dir, { recursive: true, mode: 0o700 });
185
- if (process.platform !== 'win32')
186
- await chmod(dir, 0o700);
187
- })();
172
+ const ready = hardenDirectory(dirname(this.path), 0o700);
188
173
  this.directoryReady = ready;
189
174
  void ready.catch(() => {
190
175
  if (this.directoryReady === ready)
@@ -351,9 +351,9 @@ class SqliteSessionStore {
351
351
  // pre-cutover session sees its imported rows.
352
352
  return projectHeaderSnapshot(await this.metadata.read(sessionId));
353
353
  }
354
- async readCatalogRecord(sessionId) {
354
+ async readCatalogRecord(sessionId, roleScope = 'ordinary') {
355
355
  await this.ensureCatalogProjectionReadable();
356
- const record = await this.metadata.readCatalogRecord(sessionId);
356
+ const record = await this.metadata.readCatalogRecord(sessionId, roleScope);
357
357
  return {
358
358
  ...projectHeaderSnapshot(record),
359
359
  activityAt: record.activityAt,
@@ -372,6 +372,18 @@ class SqliteSessionStore {
372
372
  await this.ensureReady();
373
373
  return this.metadata.readTranscriptHighWater(sessionId);
374
374
  }
375
+ async readCoordinationTranscriptIndexState() {
376
+ await this.ensureReady();
377
+ return this.metadata.readCoordinationTranscriptIndexState();
378
+ }
379
+ async appendCoordinationTranscriptIndex(records) {
380
+ await this.ensureReady();
381
+ return this.metadata.appendCoordinationTranscriptIndex(records);
382
+ }
383
+ async readCoordinationTranscriptIndex(request) {
384
+ await this.ensureReady();
385
+ return this.metadata.readCoordinationTranscriptIndex(request);
386
+ }
375
387
  async listTurnsSnapshot(sessionId) {
376
388
  return deriveTurnRecords(await this.readMessagesSnapshot(sessionId));
377
389
  }
@@ -16,10 +16,11 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
19
+ import { mkdir, readFile } from 'node:fs/promises';
20
20
  import { dirname, join } from 'node:path';
21
21
  import { createDefaultSettings, mergeSettings, normalizeSettings } from '@maka/core/settings';
22
22
  import { sanitizeOnboardingMilestones } from '@maka/core/onboarding';
23
+ import { writeAtomicFile } from './atomic-file-write.js';
23
24
  export function createSettingsStore(workspaceRoot) {
24
25
  return new FileSettingsStore(workspaceRoot);
25
26
  }
@@ -133,10 +134,13 @@ class FileSettingsStore {
133
134
  return result;
134
135
  }
135
136
  async write(settings) {
137
+ // SettingsStore does not own the workspace directory's permission policy:
138
+ // sibling stores such as MCP config may independently harden the same root.
139
+ // Keep both directory creation and the historical umask-derived file mode.
136
140
  await mkdir(dirname(this.settingsPath), { recursive: true });
137
- const tempPath = `${this.settingsPath}.${process.pid}.${Date.now()}.tmp`;
138
- await writeFile(tempPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
139
- await rename(tempPath, this.settingsPath);
141
+ await writeAtomicFile(this.settingsPath, JSON.stringify(settings, null, 2) + '\n', {
142
+ fileMode: 0o666 & ~process.umask(),
143
+ });
140
144
  }
141
145
  withQueue(operation) {
142
146
  const next = this.queue.then(operation, operation);
@@ -82,6 +82,17 @@ class SqliteArtifactMetadataRepository {
82
82
  .prepare('SELECT 1 FROM artifact_records WHERE relative_path = ?')
83
83
  .get(relativePath));
84
84
  }
85
+ readRelativePathsByCaseFoldedArtifactIds(artifactIds) {
86
+ this.assertOpen();
87
+ if (artifactIds.length === 0)
88
+ return [];
89
+ const placeholders = artifactIds.map(() => '?').join(', ');
90
+ const rows = this.#lease.database
91
+ .prepare(`SELECT relative_path FROM artifact_records
92
+ WHERE artifact_id COLLATE NOCASE IN (${placeholders})`)
93
+ .all(...artifactIds);
94
+ return rows.map((row) => row.relative_path);
95
+ }
85
96
  forgetUpgradeOrphanPaths(relativePaths) {
86
97
  this.assertOpen();
87
98
  this.#lease.transaction('write', () => {
@@ -16,7 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- export const SQLITE_SESSION_METADATA_SCHEMA_VERSION = 38;
19
+ export const SQLITE_SESSION_METADATA_SCHEMA_VERSION = 39;
20
20
  export const SQLITE_SESSION_MESSAGE_CHUNK_BYTES = 64 * 1024;
21
21
  export const SQLITE_SESSION_MESSAGE_CHUNK_MARKER = '{"$maka":"session-message-chunks-v1"}';
22
22
  export const SQLITE_AGENT_GRAPH_CONTROL_TABLES = [
@@ -32,6 +32,17 @@ export const SQLITE_AGENT_GRAPH_CONTROL_TABLES = [
32
32
  'agent_graph_supervisor_wake_attempts',
33
33
  ];
34
34
  const MIGRATIONS = new Map([
35
+ [
36
+ 39,
37
+ `
38
+ CREATE TABLE IF NOT EXISTS coordination_transcript_index (
39
+ sequence INTEGER PRIMARY KEY,
40
+ source TEXT NOT NULL CHECK (source IN ('legacy', 'runtime')),
41
+ source_sequence INTEGER NOT NULL CHECK (source_sequence >= 0),
42
+ UNIQUE (source, source_sequence)
43
+ );
44
+ `,
45
+ ],
35
46
  [
36
47
  1,
37
48
  `
@@ -730,10 +730,12 @@ export class SqliteSessionMetadataStore {
730
730
  throw new SessionNotFoundError(sessionId);
731
731
  return record;
732
732
  }
733
- async readCatalogRecord(sessionId) {
733
+ async readCatalogRecord(sessionId, roleScope = 'ordinary') {
734
734
  this.assertOpen();
735
735
  assertSafeSessionId(sessionId);
736
- const role = sqliteOrdinarySessionRolePredicate();
736
+ const role = roleScope === 'recoverable'
737
+ ? sqliteRecoverableSessionRolePredicate()
738
+ : sqliteOrdinarySessionRolePredicate();
737
739
  const row = this.db
738
740
  .prepare(`
739
741
  SELECT
@@ -1206,7 +1208,11 @@ export class SqliteSessionMetadataStore {
1206
1208
  assignment.id !== `wha_${suffix}` ||
1207
1209
  assignment.targetMessageId !== `whm_${suffix}` ||
1208
1210
  assignment.delegationId !== `whd_${suffix}` ||
1209
- !messageContentsEqual(admission.content, normalizeMessageContent({ text: assignment.userText })) ||
1211
+ !workHubAssignmentAttachmentsMatchTarget(assignment) ||
1212
+ !messageContentsEqual(admission.content, normalizeMessageContent({
1213
+ text: assignment.delegationText ?? assignment.userText,
1214
+ ...(assignment.targetAttachments ? { attachments: assignment.targetAttachments } : {}),
1215
+ })) ||
1210
1216
  admission.submittedContentDigest !== messageContentDigest(admission.content) ||
1211
1217
  admission.submittedPlacement !== 'current_turn' ||
1212
1218
  admission.placement !== 'current_turn' ||
@@ -1825,6 +1831,45 @@ export class SqliteSessionMetadataStore {
1825
1831
  unique.forEach((messageId, index) => update.run(index, sessionId, messageId));
1826
1832
  });
1827
1833
  }
1834
+ async readCoordinationTranscriptIndexState() {
1835
+ this.assertOpen();
1836
+ return this.db
1837
+ .prepare(`SELECT (SELECT MAX(sequence) FROM coordination_transcript_index) AS highWater,
1838
+ (SELECT MAX(source_sequence) FROM coordination_transcript_index WHERE source = 'legacy') AS legacy,
1839
+ (SELECT MAX(source_sequence) FROM coordination_transcript_index WHERE source = 'runtime') AS runtime`)
1840
+ .get();
1841
+ }
1842
+ async appendCoordinationTranscriptIndex(records) {
1843
+ this.assertOpen();
1844
+ if (records.length > 64)
1845
+ throw new Error('Coordination transcript index batch exceeds limit');
1846
+ this.transaction(() => {
1847
+ let sequence = this.db
1848
+ .prepare('SELECT MAX(sequence) AS value FROM coordination_transcript_index')
1849
+ .get().value ?? -1;
1850
+ const insert = this.db.prepare(`INSERT INTO coordination_transcript_index
1851
+ (sequence, source, source_sequence) VALUES (?, ?, ?)
1852
+ ON CONFLICT(source, source_sequence) DO NOTHING`);
1853
+ for (const record of records) {
1854
+ if (!Number.isSafeInteger(record.sourceSequence) || record.sourceSequence < 0)
1855
+ throw new Error('Invalid Coordination source sequence');
1856
+ const result = insert.run(sequence + 1, record.source, record.sourceSequence);
1857
+ if (result.changes)
1858
+ sequence++;
1859
+ }
1860
+ });
1861
+ }
1862
+ async readCoordinationTranscriptIndex(request) {
1863
+ this.assertOpen();
1864
+ if (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 64)
1865
+ throw new Error('Invalid Coordination transcript index limit');
1866
+ const older = request.direction === 'older';
1867
+ return this.db
1868
+ .prepare(`SELECT sequence, source, source_sequence AS sourceSequence
1869
+ FROM coordination_transcript_index WHERE sequence <= ? AND sequence ${older ? '<=' : '>='} ?
1870
+ ORDER BY sequence ${older ? 'DESC' : 'ASC'} LIMIT ?`)
1871
+ .all(request.throughSequence, request.position, request.limit);
1872
+ }
1828
1873
  async readMessages(sessionId) {
1829
1874
  return this.readMessagesWith(sessionId, decodeStoredMessage);
1830
1875
  }
@@ -4566,6 +4611,21 @@ function isWorkHubActionOperation(value) {
4566
4611
  value === 'replace' ||
4567
4612
  value === 'stop');
4568
4613
  }
4614
+ function workHubAssignmentAttachmentsMatchTarget(assignment) {
4615
+ const source = assignment.attachments ?? [];
4616
+ const target = assignment.targetAttachments ?? [];
4617
+ return (source.length === target.length &&
4618
+ source.every((attachment, index) => {
4619
+ const copied = target[index];
4620
+ const { ref: sourceRef, ...sourceMetadata } = attachment;
4621
+ const { ref: targetRef, ...targetMetadata } = copied;
4622
+ return (sourceRef.kind === 'session_file' &&
4623
+ sourceRef.sessionId === WORKHUB_COORDINATION_SESSION_ID &&
4624
+ targetRef.kind === 'session_file' &&
4625
+ targetRef.sessionId === assignment.targetSessionId &&
4626
+ isDeepStrictEqual(sourceMetadata, targetMetadata));
4627
+ }));
4628
+ }
4569
4629
  function sameWorkHubAssignmentRequest(existing, requested) {
4570
4630
  return isDeepStrictEqual({
4571
4631
  actionId: existing.actionId,
@@ -4574,6 +4634,8 @@ function sameWorkHubAssignmentRequest(existing, requested) {
4574
4634
  targetSessionId: existing.targetSessionId,
4575
4635
  disposition: existing.disposition,
4576
4636
  userText: existing.userText,
4637
+ delegationText: existing.delegationText,
4638
+ attachments: existing.attachments,
4577
4639
  create: existing.create,
4578
4640
  replacesActionId: existing.replacesActionId,
4579
4641
  replacesDelegationId: existing.replacesDelegationId,
@@ -4584,6 +4646,8 @@ function sameWorkHubAssignmentRequest(existing, requested) {
4584
4646
  targetSessionId: requested.targetSessionId,
4585
4647
  disposition: requested.disposition,
4586
4648
  userText: requested.userText,
4649
+ delegationText: requested.delegationText,
4650
+ attachments: requested.attachments,
4587
4651
  create: requested.create,
4588
4652
  replacesActionId: requested.replacesActionId,
4589
4653
  replacesDelegationId: requested.replacesDelegationId,
@@ -17,7 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { constants } from 'node:fs';
20
- import { lstat, open } from 'node:fs/promises';
20
+ import { chmod, lstat, mkdir, open } from 'node:fs/promises';
21
21
  import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
22
22
  const openStableFile = open;
23
23
  const lstatStableFile = lstat;
@@ -74,6 +74,19 @@ export async function syncFile(path) {
74
74
  await handle.close();
75
75
  }
76
76
  }
77
+ /**
78
+ * Create (or harden) an owner-only directory: recursive mkdir plus a
79
+ * fail-closed chmod, since mkdir's mode only applies on creation. Callers that
80
+ * store secrets use this before creating their file or update lock.
81
+ */
82
+ export async function hardenDirectory(dir, mode = 0o700) {
83
+ await mkdir(dir, { recursive: true, mode });
84
+ if (process.platform === 'win32') {
85
+ await chmod(dir, mode).catch(() => { });
86
+ return;
87
+ }
88
+ await chmod(dir, mode);
89
+ }
77
90
  export async function syncDirectoryChain(path, root, beforeSync) {
78
91
  const boundary = resolve(root);
79
92
  let current = resolve(path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maka-agent",
3
- "version": "0.2.0-dev.25.20260908",
3
+ "version": "0.2.0-dev.26.20260909",
4
4
  "description": "Apache Maka (Incubating) developer snapshot; not an Apache release.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",